﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FinanSoft.Entities;
using System.Collections;
using FinanSoft.Utilities;
using System.Xml;
using System.IO;

namespace FinanSoft.DataLayer
{
    class AccountingEntryCacheManager
    {
        #region Fields

        /// <summary>
        /// Atributo privado del tipo de la clase. Unica instancia de esta clase que existira
        /// </summary>
        private static AccountingEntryCacheManager _instance;
        int _lastAccess;
        List<AccountingEntry> _cache;
        private bool _isError;
        private String _errorMessage;
        private bool _definiteError;
        AccountingEntry lastEntry;
        #endregion

        #region Properties
        /// <summary>
        /// Boolean que determina si ha ocurrido algún error en la ejecución de los métodos de la clase
        /// </summary>
        public bool IsError
        {
            get
            {
                bool tempError = _isError;
                if (!_definiteError)
                    _isError = false;
                return tempError;
            }
        }

        /// <summary>
        ///  Boolean que determina si ha ocurrido algún error terminal en la ejecución de los métodos de la clase
        /// </summary>
        public String ErrorMessage
        {
            get
            {
                String tempError = _errorMessage;
                if (!_definiteError)
                {
                    _isError = false;
                    _errorMessage = "";
                }
                return tempError;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Constructor privado de la clase para permitir la implementacion del patron singleton
        /// </summary>
        private AccountingEntryCacheManager()
        {
            _isError = false;
            _errorMessage = "";
        }
        #endregion

        #region Methods
        /// <summary>
        /// Metodo que obtienen la instancia existente de la clase. Si esta aun no ha sido instanciada, la instancia.
        /// </summary>
        public static AccountingEntryCacheManager GetInstance()
        {
            if (_instance == null)
                _instance = new AccountingEntryCacheManager();
            return _instance;
        }

        /// <summary>
        /// Carga un conjunto de datos a la cache, de acuerdo con los parametros de busqueda del usuario, provistos como parametros a este metodo
        /// </summary>
        /// <param name="entryCode">Codigo de documento de los asientos filtrados</param>
        /// <param name="entryAccDate"> Fecha contable de los asientos filtrados</param>
        /// <param name="ref1">Campo de referencia 1 de los asientos filtrados</param>
        /// <param name="ref2">Campo de referencia 2 de los asientos filtrados</param>
        /// <param name="ref3">Campo de referencia 3 de los asientos filtrados</param>
        /// <returns>El asiento con la fecha contable mas reciente del conjunto de asientos filtrados</returns>
        public AccountingEntry UpdateCache(int entryCode, DateTime entryAccDate, string ref1, string ref2, string ref3, bool noDate)
        {
            string xmlFilter;

            xmlFilter = ConstructXmlFilter(entryCode, entryAccDate, ref1, ref2, ref3, noDate);
            lastEntry = new AccountingEntry();
            _cache = AccountingEntryDataManager.GetInstance().GetAccountingEntries(xmlFilter);

            if (_cache == null)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_ACCOUNTING_ENTRIES_CACHE_UPDATE;
                lastEntry = null;
            }

            else if (_cache.Count == 0)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_ACCOUNTING_ENTRIES_CACHE_EMPTY;
                lastEntry = null;
            }
            else
            {
                lastEntry = _cache[_cache.Count - 1];
                _lastAccess = _cache.Count - 1;
                _isError = false;
            }

            return lastEntry;
        }

        /// <summary>
        /// Construye el XML que se pasara a la base de datos para filtrar los asientos por obtener
        /// </summary>
        /// <param name="entryCode">Codigo de documento de los asientos filtrados</param>
        /// <param name="entryAccDate"> Fecha contable de los asientos filtrados</param>
        /// <param name="ref1">Campo de referencia 1 de los asientos filtrados</param>
        /// <param name="ref2">Campo de referencia 2 de los asientos filtrados</param>
        /// <param name="ref3">Campo de referencia 3 de los asientos filtrados</param>
        /// <returns>El XML con cada parametro como un nodo y representado en forma de String</returns>
        private String ConstructXmlFilter(int entryCode, DateTime entryAccDate, string ref1, string ref2, string ref3, bool noDate)
        {
            XmlDocument newDoc;
            XmlElement newElement;
            XmlAttribute newAttribute;
            string xmlData;

            //Formato de XML: <Filtros>
	        //                    <CodigoDoc Valor=""/>
	        //                    <FechaCont Valor=""/>
	        //                    <Ref1 Valor=""/>
	        //                    <Ref2 Valor=""/>
	        //                    <Ref3 Valor=""/>
            //                </Filtros>
            newDoc = new XmlDocument();
            xmlData = Constants.FILTER_NODE_BEGINNING + Constants.FILTER_NODE_ENDING;
            newDoc.Load(new StringReader(xmlData));

            //Filtro por tipo de documento
            newElement = newDoc.CreateElement(Constants.DOC_CODE_NODE_NAME);
            newAttribute = newDoc.CreateAttribute(Constants.TAG_NAME_VALUE);
            newAttribute.Value = entryCode == -1 ? "" : Convert.ToString(entryCode);
            newElement.SetAttributeNode(newAttribute);
            newDoc.DocumentElement.AppendChild(newElement);

            //Filtro por fecha de contabilizacion
            newElement = newDoc.CreateElement(Constants.ACC_DATE_NODE_NAME);
            newAttribute = newDoc.CreateAttribute(Constants.TAG_NAME_VALUE);

            if (noDate)           
                newAttribute.Value = Convert.ToString("");
            else
                newAttribute.Value = Convert.ToString(entryAccDate);
             
            newElement.SetAttributeNode(newAttribute);
            newDoc.DocumentElement.AppendChild(newElement);

            //Filtro por referencia 1
            newElement = newDoc.CreateElement(Constants.REF1_NODE_NAME);
            newAttribute = newDoc.CreateAttribute(Constants.TAG_NAME_VALUE);
            newAttribute.Value = ref1;
            newElement.SetAttributeNode(newAttribute);
            newDoc.DocumentElement.AppendChild(newElement);

            //Filtro por referencia 2
            newElement = newDoc.CreateElement(Constants.REF2_NODE_NAME);
            newAttribute = newDoc.CreateAttribute(Constants.TAG_NAME_VALUE);
            newAttribute.Value = ref2;
            newElement.SetAttributeNode(newAttribute);
            newDoc.DocumentElement.AppendChild(newElement);

            //Filtro por referencia 3
            newElement = newDoc.CreateElement(Constants.REF3_NODE_NAME);
            newAttribute = newDoc.CreateAttribute(Constants.TAG_NAME_VALUE);
            newAttribute.Value = ref3;
            newElement.SetAttributeNode(newAttribute);
            newDoc.DocumentElement.AppendChild(newElement);

            return newDoc.InnerXml;
        }

        /// <summary>
        /// Verifica si hay un entrada en la cache siguiente a la última entrada accesada
        /// </summary>
        /// <returns>Un valor de verdad correspondiente a la existencia de la entrada siguiente</returns>
        public Boolean hasNext()
        {
            return _cache.Count - 1 > _lastAccess;
        }

        /// <summary>
        /// Verifica si hay un entrada en la cache anterior a la ultima entrada accesada
        /// </summary>
        /// <returns>Un valor de verdad correspondiente a la existencia de la entrada anterior</returns>
        public Boolean hasPrevious()
        {
            return  _lastAccess > 0;
        }

        /// <summary>
        /// Retorna el siguiente asiento en la lista de asientos
        /// </summary>
        /// <returns>El objeto asiento que corresponde a la posicion en la lista del ultimo acceso + 1</returns>
        public AccountingEntry FetchNext()
        {
            if (_cache == null)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_ACCOUNTING_ENTRIES_CACHE_UPDATE;
                lastEntry = null;
            }

            else if (_cache.Count == 0)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_ACCOUNTING_ENTRIES_CACHE_EMPTY;
                lastEntry = null;
            }
            else if (_lastAccess >= _cache.Count - 1)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_ACCOUNTING_ENTRIES_CACHE_NO_NEXT_ELEMENT;
            }
            else
            {
                _lastAccess++;
                lastEntry = _cache[_lastAccess];
                _isError = false;
            }

            return lastEntry;
        }

        /// <summary>
        /// Retorna el asiento previo en la lista de asientos
        /// </summary>
        /// <returns>El objeto asiento que corresponde a la posicion en la lista del ultimo acceso - 1</returns>
        public AccountingEntry FetchPrevious()
        {
            if (_cache == null)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_ACCOUNTING_ENTRIES_CACHE_UPDATE;
                lastEntry = null;
            }

            else if (_cache.Count == 0)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_ACCOUNTING_ENTRIES_CACHE_EMPTY;
                lastEntry = null;
            }
            else if (_lastAccess == 0)
            {
                _isError = true;
                _errorMessage = Constants.ERROR_ACCOUNTING_ENTRIES_CACHE_NO_PREVIOUS_ELEMENT;
            }
            else
            {
                _lastAccess--;
                lastEntry = _cache[_lastAccess];
                _isError = false;                
            }

            return lastEntry;
        }

        #endregion

    }
}
